<?php
/*
Plugin Name: ACTO API
Plugin URI: http://actoframework.org/plugins/acto-api/
Description: An ACTO API for WordPress
Version: 0.1
Author: Martin Dyrby
Author URI: http://molamil.com/
*/

global $acto_api_dir;
$acto_api_dir = WP_PLUGIN_DIR . '/wp-acto-api';

function acto_api_init() {
  // Initialize the controller and query inspector
  global $acto_api, $acto_api_dir;

  add_filter('rewrite_rules_array', 'acto_api_rewrites');
  $acto_api = new ACTO_Controller();
}

function acto_api_activation() {
  // Add the rewrite rule on activation
  global $wp_rewrite;
  add_filter('rewrite_rules_array', 'acto_api_rewrites');
  $wp_rewrite->flush_rules();
}

function acto_api_deactivation() {
  // Remove the rewrite rule on deactivation
  global $wp_rewrite;
  $wp_rewrite->flush_rules();
}

function acto_api_rewrites($wp_rules) {
  // Register the rewrite rule /api/[method] => ?acto=[method]
  $acto_api_rules = array(
    'api/(.+)$' => 'index.php?acto=$matches[1]'
  );
  print_r(array_merge($acto_api_rules, $wp_rules));
  return array_merge($acto_api_rules, $wp_rules);
}

// Add initialization and activation hooks
add_action('init', 'acto_api_init');
register_activation_hook("$acto_api_dir/acto-api.php", 'acto_api_activation');
register_deactivation_hook("$acto_api_dir/acto-api.php", 'acto_api_deactivation');


Class ACTO_Controller {
	var $vars = array(
		'acto',          // Determines which API controller method will be called
                     	//   Expects one of the following values:
                     	//     * 'get_recent_posts'
                     	//     * 'get_post'
                     	//     * 'get_page'
                     	//     * 'get_date_posts'
                     	//     * 'get_category_posts'
                     	//     * 'get_tag_posts'
                     	//     * 'get_author_posts'
                     	//     * 'get_date_index'
                     	//     * 'get_category_index'
                     	//     * 'get_tag_index'
                     	//     * 'get_author_index'
                     	//     * 'get_search_results'
                     	//     * 'submit_comment'
                     	//     * Any non-empty value to trigger the API implicitly
		'callback',      // Triggers a JSONP-style callback with the resulting data
    	'dev',           // Triggers a developer-friendly print_r() output
    	'redirect',      // Triggers a redirect response to the specified URL
    	'page',          // Returns a specific page of results
    	'count',         // Controls the number of posts returned
    	'post_id',       // Used by the get_post API method
    	'post_slug',     // Used by the get_post API method
    	'page_id',       // Used by the get_page API method
    	'page_slug',     // Used by the get_page API method
    	'search',        // Used by the get_search_results API method
    	'category_id',   // Used by get_category_posts API method
    	'category_slug', // Used by get_category_posts API method
    	'tag_id',        // Used by get_tag_posts API method
    	'tag_slug',      // Used by get_tag_posts API method
    	'author_id',     // Used by get_author_posts API method
    	'author_slug',   // Used by get_author_posts API method
    	'date',         // Used by get_date_posts API method
                     	//   Expects 'YYYY', 'YYYYMM' or 'YYYYMMDD'
    	'date_format',  // Changes the format of date values
                     	//   Uses the same syntax as PHP's date() function
                     	//   Default value is Y-m-d H:i:s
    	'read_more',    // Changes the 'read more' link text in post content
    	'include',       // Specifies which post data fields to include
                     //   Expects a comma-separated list of post fields
                     //   Leaving this empty includes *all* fields
    	'exclude',       // Specifies which post data fields to exclude
                     //   Expects a comma-separated list of post fields
    	'custom_fields', // Includes values from posts' Custom Fields
                     //   Expects a comma-separated list of custom field keys
    	'author_meta'    // Includes additional author metadata
                     //   Should be a comma-separated list of metadata fields

    // Note about include/exclude vars:
    //   The default behavior includes all post values. You only need to
    //   specify one of include or exclude -- the former will implicitly leave
    //   out those fields you haven't specified and the latter will implicitly
    //   include them. For example, a query that uses exclude=comments will
    //   include everything *except* the comments, so there's no need to also
	//   specify anything with the include argument.

	);
	
	function ACTO_Controller() {
 		add_filter('query_vars', array(&$this, 'query_vars'));

		add_action('template_redirect', array(&$this, 'template_redirect'));
	}
	
	function query_vars($wp_vars) {
		// This makes our variables available from the get_query_var WP function
		return array_merge($wp_vars, $this->vars);
	}
	
	function template_redirect() {
		$method = $this->get_method();

		if($method) {
			
			do_action("acto_api_$method");
			
			$result = $this->$method();
			
 			echo $result;
		
			exit;
		}
	}
	
	function get_method() {
		$this->method = get_query_var('acto');

		if (empty($this->method)) {
			return false;
		} else if (method_exists('ACTO_Controller', $this->method)) {
			return $this->method;
		} else if (is_search()) {
			return 'get_search_results';
		} else if (is_home()) {
			return 'get_recent_posts';
		} else if (is_page()) {
			return 'get_page';
		} else if (is_single()) {
			return 'get_post';
		} else if (is_category()) {
			return 'get_category_posts';
		} else if (is_tag()) {
			return 'get_tag_posts';
		} else if (is_author()) {
			return 'get_author_posts';
		} else if (is_date()) {
			return 'get_date_posts';
		} else {
			return 'error';
		}
	}
	
	function get_page() {
	    $query = '';
		$pageID = get_query_var('page_id');
		$pageName = get_query_var('pagename');
		
	    if ($pageID) {
	      $query = "page_id={$pageID}";
	    } else if ($pageName) {
	      $query = "pagename={$pageName}";
	    } else {
	      $this->error("No page specified. Include 'page_id' or 'page_slug' var in your request.");
	    }

	    $pages = $this->get_posts($query);

	    // Workaround for https://core.trac.wordpress.org/ticket/12647
	    if (empty($pages)) {
	      $url = $_SERVER['REQUEST_URI'];
	      $parsed_url = parse_url($url);
	      $path = $parsed_url['path'];
	      if (preg_match('#^http://[^/]+(/.+)$#', get_bloginfo('url'), $matches)) {
	        $blog_root = $matches[1];
	        $path = preg_replace("#^$blog_root#", '', $path);
	      }
	      if (substr($path, 0, 1) == '/') {
	        $path = substr($path, 1);
	      }
	      $pages = $this->get_posts("pagename=$path");
	    }

	    if (count($pages) == 1) {
	      return $this->get_acto(array(
	        'page' => $pages[0]
	      ));
	    } else {
	      $this->error("No page was found.");
	    }
	}
	
	function get_posts($query = '') {
		global $post;
		// Returns an array of Post objects
		$this->set_posts_query($query);
		$output = array();
		while (have_posts()) {
			the_post();
			$output[] = new Post($post);
    	}
		return $output;
	}
	
	function set_posts_query($query = '') {
		// Returns a query string to pass to WP's query_posts() function
		if (get_query_var('page')) {
			$amp = empty($query) ? '' : '&';
			$query .= "{$amp}paged=" . get_query_var('page');
		}
		if (get_query_var('count')) {
			$amp = empty($query) ? '' : '&';
			$query .= "{$amp}posts_per_page=" . get_query_var('count');
		}
		if (!empty($query)) {
			query_posts($query);
		}
  }

	
	function get_acto($data, $status = 'ok') {
		// Include a status value with the response
		if (is_array($data)) {
			$data = array_merge(array('status' => $status), $data);
		} else if (is_object($data)) {
			$data = get_object_vars($data);
			$data = array_merge(array('status' => $status), $data);
		}

		$data = apply_filters('json_api_encode', $data);

		if (!empty($_REQUEST['dev'])) {
			if (!headers_sent()) {
	        	header('Content-Type: text/plain; charset: UTF-8', true);
	      	}
			print "----- ACTO developer -----\n";
	      	print_r($data);
			exit;
		} else {
			return $this->acto_encode($data);
		}
	}


	function acto_encode($data) {
	    $charset = get_option('blog_charset');
	    if (!headers_sent()) {
	      header("Content-Type: application/xml; charset=$charset", true);
	    }
		
		return '<?xml version="1.0" encoding="'.$charset.'" ?>
<acto>
	<pages>
		<page id="'.$data['page']->title.'">
			<prop name="content"><![CDATA['.$data['page']->content.']]></prop>
		</page>
	</pages>
</acto>';
	}
}


Class Post {
	
	var $id;              // Integer
	var $slug;            // String
	var $url;             // String
	var $status;          // String ("draft", "published", or "pending")
	var $title;           // String
	var $title_plain;     // String
	var $content;         // String (modified by read_more query var)
	var $excerpt;         // String
	var $date;            // String (modified by date_format query var)
	var $modified;        // String (modified by date_format query var)
	var $categories;      // Array of objects
	var $tags;            // Array of objects
	var $author;          // Object
	var $comments;        // Array of objects
	var $attachments;     // Array of objects
	var $comment_count;   // Integer
	var $comment_status;  // String ("open" or "closed")
	var $thumbnail;       // String
	var $custom_fields;   // Object (included by using custom_fields query var)

	function Post($wp_post = null) {
		if (!empty($wp_post)) {
			$this->import_wp_object($wp_post);
		}
	}

	function create($values = null) {
		unset($values['id']);
		if (empty($values) || empty($values['title'])) {
			$values = array(
				'title' => 'Untitled',
				'content' => ''
				);
		}
		return $this->save($values);
	}

	function update($values) {
		$values['id'] = $this->id;
		return $this->save($values);
	}

	function save($values = null) {
		global $user_ID;

		$wp_values = array();

		if (!empty($values['id'])) {
			$wp_values['ID'] = $values['id'];
		}

		if (!empty($values['status'])) {
			$wp_values['post_status'] = $values['status'];
		}

		if (!empty($values['title'])) {
			$wp_values['post_title'] = $values['title'];
		}

		if (!empty($values['content'])) {
			$wp_values['post_content'] = $values['content'];
		}

		if (!empty($values['author'])) {
			$author = $json_api->introspector->get_author_by_login($values['author']);
			$wp_values['post_author'] = $author->id;
		}

		if (isset($values['categories'])) {
			$categories = explode(',', $values['categories']);
			foreach ($categories as $category_slug) {
				$category_slug = trim($category_slug);
				$category = $json_api->introspector->get_category_by_slug($category_slug);
				if (empty($wp_values['post_category'])) {
					$wp_values['post_category'] = array($category->id);
				} else {
					array_push($wp_values['post_category'], $category->id);
				}
			}
		}

		if (isset($values['tags'])) {
			$tags = explode(',', $values['tags']);
			foreach ($tags as $tag_slug) {
				$tag_slug = trim($tag_slug);
				if (empty($wp_values['tags_input'])) {
					$wp_values['tags_input'] = array($tag_slug);
				} else {
					array_push($wp_values['tags_input'], $tag_slug);
				}
			}
		}

		if (isset($wp_values['ID'])) {
			$this->id = wp_update_post($wp_values);
		} else {
			$this->id = wp_insert_post($wp_values);
		}

		if (!empty($_FILES['attachment'])) {
			include_once ABSPATH . '/wp-admin/includes/file.php';
			include_once ABSPATH . '/wp-admin/includes/media.php';
			include_once ABSPATH . '/wp-admin/includes/image.php';
			$attachment_id = media_handle_upload('attachment', $this->id);
			$this->attachments[] = new Attachment($attachment_id);
			unset($_FILES['attachment']);
		}

		$wp_post = get_post($this->id);
		$this->import_wp_object($wp_post);

		return $this->id;
	}

	function import_wp_object($wp_post) {
		global $post;
		
		$date_format = "Y-m-d H:i:s";
		
		$this->id = (int) $wp_post->ID;
		setup_postdata($wp_post);
		$this->set_value('slug', $wp_post->post_name);
		$this->set_value('url', get_permalink($this->id));
		$this->set_value('status', $wp_post->post_status);
		$this->set_value('title', get_the_title($this->id));
		$this->set_value('title_plain', strip_tags($this->title));
		$this->set_content_value();
		$this->set_value('excerpt', get_the_excerpt());
		$this->set_value('date', get_the_time($date_format));
		$this->set_value('modified', date($date_format, strtotime($wp_post->post_modified)));
		$this->set_categories_value();
		$this->set_tags_value();
		$this->set_author_value($wp_post->post_author);
		$this->set_comments_value();
		$this->set_attachments_value();
		$this->set_value('comment_count', (int) $wp_post->comment_count);
		$this->set_value('comment_status', $wp_post->comment_status);
		$this->set_thumbnail_value();
		$this->set_custom_fields_value();
	}

	function set_value($key, $value) {
		$this->$key = $value;
	}

	function set_content_value() {
		$content = get_the_content(get_query_var("read_more"));
		$content = apply_filters('the_content', $content);
		$content = str_replace(']]>', ']]&gt;', $content);
		$this->content = $content;
	}

	function set_categories_value() {
		$this->categories = array();
		if ($wp_categories = get_the_category($this->id)) {
			foreach ($wp_categories as $wp_category) {
				$category = new Category($wp_category);
				if ($category->id == 1 && $category->slug == 'uncategorized') {
					// Skip the 'uncategorized' category
					continue;
				}
				$this->categories[] = $category;
			}
		}
	}

	function set_tags_value() {
		$this->tags = array();
		if ($wp_tags = get_the_tags($this->id)) {
			foreach ($wp_tags as $wp_tag) {
				$this->tags[] = new Tag($wp_tag);
			}
		}
	}

	function set_author_value($author_id) {
		
	}

	function set_comments_value() {
		$this->comments = "";
	}

	function set_attachments_value() {
	}

	function set_thumbnail_value() {

	}

	function set_custom_fields_value() {
		$keys = explode(',', get_query_var('custom_fields'));
		$wp_custom_fields = get_post_custom($this->id);
		$this->custom_fields = new stdClass();
		foreach ($keys as $key) {
			if (isset($wp_custom_fields[$key])) {
				$this->custom_fields->$key = $wp_custom_fields[$key];
			}
		}
	}
	
	function get_author_by_login($login) {
		global $wpdb;
		$id = $wpdb->get_var($wpdb->prepare("
			SELECT ID
			FROM $wpdb->users
			WHERE user_login = %s
			", $login));
		return $this->get_author_by_id($id);
	}
}

class Tag {
  
  var $id;          // Integer
  var $slug;        // String
  var $title;       // String
  var $description; // String
  
  function Tag($wp_tag = null) {
    if ($wp_tag) {
      $this->import_wp_object($wp_tag);
    }
  }
  
  function import_wp_object($wp_tag) {
    $this->id = (int) $wp_tag->term_id;
    $this->slug = $wp_tag->slug;
    $this->title = $wp_tag->name;
    $this->description = $wp_tag->description;
    $this->post_count = (int) $wp_tag->count;
  }
  
}


class Category {
  
  var $id;          // Integer
  var $slug;        // String
  var $title;       // String
  var $description; // String
  var $parent;      // Integer
  var $post_count;  // Integer
  
  function Category($wp_category = null) {
    if ($wp_category) {
      $this->import_wp_object($wp_category);
    }
  }
  
  function import_wp_object($wp_category) {
    $this->id = (int) $wp_category->term_id;
    $this->slug = $wp_category->slug;
    $this->title = $wp_category->name;
    $this->description = $wp_category->description;
    $this->parent = (int) $wp_category->parent;
    $this->post_count = (int) $wp_category->count;
  }
  
}

class Attachment {
  
  var $id;          // Integer
  var $url;         // String
  var $slug;        // String
  var $title;       // String
  var $description; // String
  var $caption;     // String
  var $parent;      // Integer
  var $mime_type;   // String
  
  function Attachment($wp_attachment = null) {
    if ($wp_attachment) {
      $this->import_wp_object($wp_attachment);
      if ($this->is_image()) {
        $this->query_images();
      }
    }
  }
  
  function import_wp_object($wp_attachment) {
    $this->id = (int) $wp_attachment->ID;
    $this->url = $wp_attachment->guid;
    $this->slug = $wp_attachment->post_name;
    $this->title = $wp_attachment->post_title;
    $this->description = $wp_attachment->post_content;
    $this->caption = $wp_attachment->post_excerpt;
    $this->parent = (int) $wp_attachment->post_parent;
    $this->mime_type = $wp_attachment->post_mime_type;
  }
  
  function is_image() {
    return (substr($this->mime_type, 0, 5) == 'image');
  }
  
  function query_images() {
    $sizes = array('thumbnail', 'medium', 'large', 'full');
    $this->images = array();
    foreach ($sizes as $size) {
      list($url, $width, $height) = wp_get_attachment_image_src($this->id, $size);
      $this->images[$size] = (object) array(
        'url' => $url,
        'width' => $width,
        'height' => $height
      );
    }
  }
  
}


?>
